﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.Threading.Tasks;

using alib.Debugging;
using alib.Enumerable;

using agree.schema;

namespace agree.semi
{
	using sext = alib.String._string_ext;

	public enum VpmOperator
	{
		Both = 0,
		Forward = 1,
		Backward = 2,
	};

	public enum VpmArgumentKind
	{
		Type,
		Default,		// '*'
		Omit,			// '!'
		DefaultForType	// '[e]' 
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class vpm_part
	{
		protected vpm_part(Vpm vpm)
		{
			this.vpm = vpm;
		}
		protected readonly Vpm vpm;
	}


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public class VpmRule : vpm_part
	{
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		static readonly String[] op_string_map;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		static readonly _string_op_map string_op_map;

		sealed class _string_op_map
		{
			public VpmOperator this[String s]
			{
				get
				{
					for (int i = 0; i < op_string_map.Length; i++)
						if (s == op_string_map[i])
							return (VpmOperator)i;
					return (VpmOperator)(-1);
				}
			}
		};

		static VpmRule()
		{
			op_string_map = new String[3];
			op_string_map[(int)VpmOperator.Both] = "<>";
			op_string_map[(int)VpmOperator.Forward] = ">>";
			op_string_map[(int)VpmOperator.Backward] = "<<";

			string_op_map = new _string_op_map();
		}

		public VpmRule(Vpm vpm, String line)
			: base(vpm)
		{
			var parts = line.Split(default(char[]), StringSplitOptions.RemoveEmptyEntries);
			int op_idx = alib.Array.arr.IndexofAny(parts, op_string_map);
			if (op_idx == -1)
				throw new Exception(String.Format("Did not find an operator in the rule ('<<', '>>', or '<>'): {0}", line));

			if (op_idx == 0 || op_idx == parts.Length - 1)
				throw new Exception(String.Format("Must be at least one argument to the left and right of the operator: {0}", line));

			this.op = string_op_map[parts[op_idx]];
			if (op < 0)
				throw new Exception(String.Format("invalid VPM operator found: {0}", parts[op_idx]));

			int i, j;

			i = 0;
			this.left = new VpmArgument[op_idx];
			for (j = 0; j < left.Length; j++)
				left[j] = VpmArgument.Parse(vpm, parts[i++]);

			i++;
			this.right = new VpmArgument[parts.Length - i];
			for (j = 0; j < right.Length; j++)
				right[j] = VpmArgument.Parse(vpm, parts[i++]);
		}

		public readonly VpmOperator op;
		public readonly VpmArgument[] left, right;

		public override String ToString()
		{
			return String.Format("{0} {1} {2}", left.StringJoin(" "), op_string_map[(int)op], right.StringJoin(" "));
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public class VpmArgument
	{
		public VpmArgument(VpmArgumentKind kind, String type)
		{
			this.Kind = kind;
			this.TypeName = type;
		}

		public VpmArgumentKind Kind { get; set; }
		public String TypeName { get; set; }

		public override String ToString()
		{
			switch (this.Kind)
			{
				case VpmArgumentKind.Default:
					return "*";
				case VpmArgumentKind.Omit:
					return "!";
				case VpmArgumentKind.DefaultForType:
					return String.Format("[{0}]", TypeName);
				case VpmArgumentKind.Type:
				default:
					return TypeName;
			}
		}

		public static VpmArgument Default = new VpmArgument(VpmArgumentKind.Default, null);
		public static VpmArgument Omit = new VpmArgument(VpmArgumentKind.Omit, null);

		public static VpmArgument Parse(Vpm vpm, String tok)
		{
			if (tok == "*")
				return Default;
			else if (tok == "!")
				return Omit;
			else if (tok != (tok = sext.RemoveOuterSquareBackets(tok)))
				return new VpmArgument(VpmArgumentKind.DefaultForType, tok);
			else
				return new VpmArgument(VpmArgumentKind.Type, tok);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	[DebuggerDisplay("{ToString(),nq}")]
	public sealed class VpmSectionHeader : vpm_part
	{
		//#warning remove test code
		//		public static HashSet<String> props = new HashSet<String>();

		public static readonly VpmSectionHeader EmptyMapping;

		static VpmSectionHeader() { EmptyMapping = new VpmSectionHeader(); }

		VpmSectionHeader()
			: base(null)
		{
			this.LeftProps = new[] { BootstrapFsPath.Empty };
			this.RightProps = new[] { String.Empty };
		}

		public VpmSectionHeader(Vpm vpm, String line)
			: base(vpm)
		{
			var parts = line.Split(default(char[]), StringSplitOptions.RemoveEmptyEntries);

			int colon_idx = Array.IndexOf(parts, ":");
			if (colon_idx == -1)
				throw new Exception(String.Format("Did not find a ':' in VPM section header: {0}", line));

			if (colon_idx == 0 || colon_idx == parts.Length - 1)
				throw new Exception(String.Format("Must be at least one property to the left and right of the colon: {0}", line));

			int i, j;

			i = 0;
			LeftProps = new BootstrapFsPath[colon_idx];
			for (j = 0; j < LeftProps.Length; j++)
			{
				//#warning remove test code
				//				lock (props)
				//				{
				//					String ss = parts[i].ToUpper();
				//					if (props.Add(ss))
				//						Debug.WriteLine(ss);
				//				}

				LeftProps[j] = new BootstrapFsPath(vpm, parts[i++]);
			}

			i++;
			RightProps = new String[parts.Length - i];
			for (j = 0; j < RightProps.Length; j++)
				RightProps[j] = parts[i++];
		}

		public readonly BootstrapFsPath[] LeftProps;
		public readonly String[] RightProps;

		public override String ToString()
		{
			return String.Format("{0} : {1}", LeftProps.StringJoin(" "), RightProps.StringJoin(" "));
		}
	};

	public abstract class VpmSection : vpm_part, IReadOnlyList<VpmRule>
	{
		public VpmSection(Vpm vpm, VpmSectionHeader header)
			: base(vpm)
		{
			this.Header = header;
		}

		public readonly VpmSectionHeader Header;

		public void AddRule(VpmRule rule)
		{
			if (rule.left.Length != Header.LeftProps.Length || rule.right.Length != Header.RightProps.Length)
				throw new Exception("number of properties in the rule does not match the section header");

			alib.Array.arr.Append(ref arr, rule);
		}

		VpmRule[] arr;

		public VpmRule this[int index] { get { return arr[index]; } }

		public int Count { get { return arr.Length; } }

		public IEnumerator<VpmRule> GetEnumerator() { return arr.Enumerator(); }

		IEnumerator IEnumerable.GetEnumerator() { return arr.Enumerator(); }

		public override String ToString()
		{
			return Header.ToString() + " " + this.StringJoin(", ");
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public sealed class VpmVarMapSection : VpmSection
	{
		public VpmVarMapSection(Vpm vpm)
			: base(vpm, VpmSectionHeader.EmptyMapping)
		{
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public sealed class VpmRuleSection : VpmSection
	{
		public VpmRuleSection(Vpm vpm, String line)
			: base(vpm, new VpmSectionHeader(vpm, line))
		{
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class Vpm : identity_named
	{
		public Vpm(IIdentity parent)
			: base(parent)
		{
			VariableMap = new VpmVarMapSection(this);

			Sections = new List<VpmRuleSection>();
		}

		public VpmVarMapSection VariableMap { get; set; }

		public String SourceFileName { get; set; }

		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		public List<VpmRuleSection> Sections { get; set; }

		public Object Loader
		{
			set
			{
				var _tmp = (Vpm)value;
				this.VariableMap = _tmp.VariableMap;
				this.Sections = _tmp.Sections;
				this.SourceFileName = _tmp.SourceFileName;
			}
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class VpmLoader : _monad<String>,
		ρρFilename
	{
		public VpmLoader(IRtParent parent, String filename)
			: base(parent, filename)
		{
		}
		public VpmLoader(IRtParent parent)
			: this(parent, default(String))
		{
		}


		///////////////////////////////////////////////////////////////////////
		/// 
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public String Filename
		{
			get { return base.t0; }
			set { base.t0 = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////


		public override Object ProvideValue(IServiceProvider sp)
		{
			var fi = DirectoryPathSet.FindFileInfo(this, t0);
			if (fi == null)
				throw new FileNotFoundException("Vpm file not found");
			var f = fi.FullName;

			var vpm = new Vpm(this) { SourceFileName = f };

			load_vpm_file(vpm, File.ReadAllLines(f));

			return vpm;
		}

		void load_vpm_file(Vpm vpm, IEnumerable<String> lines)
		{
			VpmSection cur_section = vpm.VariableMap;
			VpmRuleSection vrs;

			foreach (var _line in lines)
			{
				int ix;
				var line = (ix = _line.IndexOf(';')) == -1 ? _line : _line.Substring(0, ix);
				line = line.Trim();
				if (line.Length == 0)
					continue;

				if (line.Contains(" : "))
				{
					if ((vrs = cur_section as VpmRuleSection) != null)
						vpm.Sections.Add(vrs);

					cur_section = new VpmRuleSection(vpm, line);
				}
				else
				{
					var rule = new VpmRule(vpm, line);
					cur_section.AddRule(rule);
				}
			}

			if ((vrs = cur_section as VpmRuleSection) != null)
				vpm.Sections.Add(vrs);
		}
	};
}
